package com.allwees.sender.sms;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.allwees.core.cache.RedisUtil;
import com.allwees.core.common.context.EnvironmentHelper;
import com.allwees.sender.exception.SmsException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;

/**
 * 短信发送服务
 * todo 短信发送服务待重构
 *
 * @author yangylsky
 * @since 2019-12-25
 */
@Slf4j
@Data
@Service
public class SmsSender implements SmsCodeVerifier {
    /**
     * 产品名称:云通信短信API产品,开发者无需替换
     */
    private static final String product = "Dysmsapi";

    /**
     * 短信API产品域名（接口地址固定，无需修改）
     */
    private static final String domain = "dysmsapi.aliyuncs.com";

    /**
     * 短信防护KEY
     */
    private static final String MOBILE_CONFINE_PREFIX = "sms:MOBILE_CONFINE::";

    private static final String MOBILE_VERIFY_CODE_PREFIX = "sms:VERIFY_CODE::";

    @Value("${ali-sms.accessKeyId:}")
    private String accessKeyId;

    @Value("${ali-sms.accessKeySecret:}")
    private String accessKeySecret;

    //*****************************************模板配置读取***********************begin

    /**
     * 商家用户申请入驻
     */
    @Value("${ali-sms.templateCode-merchant-apply:}")
    private String templateCode_merchant_apply;

    /**
     * 下发点是客户支付成功金额确认
     */
    @Value("${ali-sms.templateCode-confirm-amount:}")
    private String templateCode_confirm_amount;


    @Value("${ali-sms.templateCode_overtime_un_shipped:}")
    private String templateCode_overtime_un_shipped;

    @Value("${ali-sms.templateCode_overtime_un_shipped_refund:}")
    private String templateCode_overtime_un_shipped_refund;

    /**
     * 提现确认-提醒短信
     */
    @Value("${ali-sms.templateCode-confirm-withdrawal:}")
    private String templateCode_confirm_withdrawal;


    /**
     * 申请提现-提醒短信
     */
    @Value("${ali-sms.templateCode-apply-withdrawal:}")
    private String templateCode_apply_withdrawal;

    /**
     * 提现成功-提醒短信
     */
    @Value("${ali-sms.templateCode-success-withdrawal:}")
    private String templateCode_success_withdrawal;

    /**
     * 重置提现密码短信
     */
    @Value("${ali-sms.templateCode-reset-withdrawal-password:}")
    private String templateCode_reset_withdrawal_password;


    //*****************************************模板配置读取************************end

    @Value("${ali-sms.signatrue:}")
    private String signatrue;

    @Value("${ali-sms.captchaLength:4}")
    private Integer captchaLength;

    @Value("${ali-sms.captchaTtl:300}")
    private Integer captchaTtl;

    private IAcsClient acsClient;

    @Resource
    private EnvironmentHelper environmentHelper;

    @SuppressWarnings("deprecation")
    @PostConstruct
    private void init() {
        // 设置超时时间-可自行调整
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");
        // 初始化ascClient,暂时不支持多region（请勿修改）
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
        try {
            DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
            acsClient = new DefaultAcsClient(profile);
        } catch (ClientException e) {
            log.error("SmsSender init failed.");
        }
    }

    //RETAILER_REGISTER_APPLY 入驻短信
    //WITHDRAW 提现
    //SET_WITHDRAW_SECURITY_INFO 重置提现密码

    /**
     * 发送短信总入口
     *
     * @param bsType
     * @param mobile
     * @param ip
     * @return
     */
    public SendResult sendSms(String bsType, String mobile, String ip) {
        if (environmentHelper.isNotProduct()) {
            return SendResult.success("0");
        }
        SendResult sendResult = null;

        switch (bsType) {
            case "RETAILER_REGISTER_APPLY":
                sendResult = merchantApplySendSms(bsType, mobile, ip);
                break;
            case "SET_WITHDRAW_SECURITY_INFO":
                sendResult = resetWithdrawalPasswordSendSms(bsType, mobile, ip);
                break;
            case "WITHDRAW":
                sendResult = confirmWithdrawalApplySendSms(bsType, mobile, ip);
                break;
        }

        return sendResult;
    }


    /**
     * 发送商户入驻验证码短信
     *
     * @param bsType
     * @param mobile
     * @param ip
     * @return
     */
    private SendResult merchantApplySendSms(String bsType, String mobile, String ip) {
        if (environmentHelper.isNotProduct()) {
            return SendResult.success("0");
        }
        String code = RandomStringUtils.randomNumeric(captchaLength);
        SendResult sendResult;
        if (ArrayUtils.contains(MOCK_MOBILES, mobile)) {
            code = MOCK_SMSCODE;
            sendResult = SendResult.success(code);
        } else {
            checkConfineSms(mobile, ip);
            String codeFormat = "{\"code\":\"" + code + "\"}";
            sendResult = sendAliSms(mobile, templateCode_merchant_apply, code, codeFormat);
        }
        if (sendResult.isSuccess()) {
            String key = getVerifyCodeKey(bsType, mobile);
            RedisUtil.set(key, code, captchaTtl);
            log.info("mobile {} 的手机验证码：{{}: {}}", mobile, key, code);
            sendResult.setMsg("验证码已成功发送，请在" + (captchaTtl / 60) + "分钟内完成验证");
        }

        return sendResult;
    }

    /**
     * 发送商户提现验证码短信
     *
     * @param bsType
     * @param mobile
     * @param ip
     * @return
     */
    private SendResult confirmWithdrawalApplySendSms(String bsType, String mobile, String ip) {
        if (environmentHelper.isNotProduct()) {
            return SendResult.success("0");
        }
        String code = RandomStringUtils.randomNumeric(captchaLength);
        SendResult sendResult;
        if (ArrayUtils.contains(MOCK_MOBILES, mobile)) {
            code = MOCK_SMSCODE;
            sendResult = SendResult.success(code);
        } else {
            checkConfineSms(mobile, ip);
            String codeFormat = "{\"code\":\"" + code + "\"}";
            sendResult = sendAliSms(mobile, templateCode_confirm_withdrawal, code, codeFormat);
        }
        if (sendResult.isSuccess()) {
            String key = getVerifyCodeKey(bsType, mobile);
            RedisUtil.set(key, code, captchaTtl);
            log.info("mobile {} 的手机验证码：{{}: {}}", mobile, key, code);
            sendResult.setMsg("验证码已成功发送，请在" + (captchaTtl / 60) + "分钟内完成验证");
        }

        return sendResult;
    }


    /**
     * 重置提现密码短信
     *
     * @param bsType
     * @param mobile
     * @param ip
     * @return
     */
    private SendResult resetWithdrawalPasswordSendSms(String bsType, String mobile, String ip) {
        if (environmentHelper.isNotProduct()) {
            return SendResult.success("0");
        }
        String code = RandomStringUtils.randomNumeric(captchaLength);
        SendResult sendResult;
        if (ArrayUtils.contains(MOCK_MOBILES, mobile)) {
            code = MOCK_SMSCODE;
            sendResult = SendResult.success(code);
        } else {
            checkConfineSms(mobile, ip);
            String codeFormat = "{\"code\":\"" + code + "\"}";
            sendResult = sendAliSms(mobile, templateCode_reset_withdrawal_password, code, codeFormat);
        }
        if (sendResult.isSuccess()) {
            String key = getVerifyCodeKey(bsType, mobile);
            RedisUtil.set(key, code, captchaTtl);
            log.info("mobile {} 的手机验证码：{{}: {}}", mobile, key, code);
            sendResult.setMsg("验证码已成功发送，请在" + (captchaTtl / 60) + "分钟内完成验证");
        }

        return sendResult;
    }


    /**
     * 客户支付成功通知商家金额确认
     *
     * @param mobile
     * @param amount
     * @return
     */
    public SendResult confirmAmountSendSms(String mobile, String amount) {
        if (environmentHelper.isNotProduct()) {
            return SendResult.success("0");
        }
        String code = "";
        String codeFormat = "{\"amount\":" + amount + "}";
        return sendAliSms(mobile, templateCode_confirm_amount, code, codeFormat);
    }

    public SendResult merchantUnShippedNotifySendSms(String mobile, Integer unShippedNum, Integer overTimeUnShipped) {
        if (environmentHelper.isNotProduct()) {
            return SendResult.success("0");
        }
        String code = "";
        String codeFormat = "{" +
                "    \"unShippedNum\": \"" + unShippedNum + "\"," +
                "    \"overTimeUnShipped\": \"" + overTimeUnShipped + "\"" +
                "}";
        return sendAliSms(mobile, templateCode_overtime_un_shipped, code, codeFormat);
    }

    public SendResult merchantUnShippedRefundNotifySendSms(String mobile, Integer unShippedNum) {
        if (environmentHelper.isNotProduct()) {
            return SendResult.success("0");
        }
        String code = "";
        String codeFormat = "{" +
                "    \"unShippedNum\": \"" + unShippedNum + "\"" +
                "}";
        return sendAliSms(mobile, templateCode_overtime_un_shipped_refund, code, codeFormat);
    }

    /**
     * 提现成功通知
     *
     * @param mobile
     * @param amount
     * @return
     */
    public SendResult withdrawalSuccessSendSms(String mobile, String amount) {
        if (environmentHelper.isNotProduct()) {
            return SendResult.success("0");
        }
        String bsType = "";
        String code = RandomStringUtils.randomNumeric(captchaLength);
        SendResult sendResult;
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String sendTime = df.format(System.currentTimeMillis());

        if (ArrayUtils.contains(MOCK_MOBILES, mobile)) {
            code = MOCK_SMSCODE;
            sendResult = SendResult.success(code);
        } else {
            String codeFormat = "{" +
                    "    \"code\": \"" + code + "\"," +
                    "    \"time\": \"" + sendTime + "\"," +
                    "    \"amount\": \"" + amount + "\"" +
                    "}";
            sendResult = sendAliSms(mobile, templateCode_success_withdrawal, code, codeFormat);
        }

        if (sendResult.isSuccess()) {
            String key = getVerifyCodeKey(bsType, mobile);
            RedisUtil.set(key, code, captchaTtl);
            log.info("mobile {} 的手机验证码：{{}: {}}", mobile, key, code);
            sendResult.setMsg("验证码已成功发送，请在" + (captchaTtl / 60) + "分钟内完成验证");
        }

        return sendResult;

    }

    /**
     * 提现成功通知
     *
     * @param mobile
     * @param amount
     * @return
     */
    public SendResult applyWithdrawalSendSms(String mobile, String amount) {
        if (environmentHelper.isNotProduct()) {
            return SendResult.success("0");
        }
        String bsType = "";
        String code = RandomStringUtils.randomNumeric(captchaLength);
        SendResult sendResult;
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String sendTime = df.format(System.currentTimeMillis());
        if (ArrayUtils.contains(MOCK_MOBILES, mobile)) {
            code = MOCK_SMSCODE;
            sendResult = SendResult.success(code);
        } else {
            String codeFormat = "{" +
                    "    \"code\": \"" + code + "\"," +
                    "    \"time\": \"" + sendTime + "\"," +
                    "    \"amount\": \"" + amount + "\"" +
                    "}";
            sendResult = sendAliSms(mobile, templateCode_apply_withdrawal, code, codeFormat);
        }

        if (sendResult.isSuccess()) {
            String key = getVerifyCodeKey(bsType, mobile);
            RedisUtil.set(key, code, captchaTtl);
            log.info("mobile {} 的手机验证码：{{}: {}}", mobile, key, code);
            sendResult.setMsg("验证码已成功发送，请在" + (captchaTtl / 60) + "分钟内完成验证");
        }

        return sendResult;

    }


    static String getVerifyCodeKey(String bsType, String mobile) {
        return MOBILE_VERIFY_CODE_PREFIX + bsType + "_" + mobile;
    }

    /**
     * 短信验证码防护
     *
     * @param mobile
     * @param ipAddress
     */
    private void checkConfineSms(String mobile, String ipAddress) {
        long confinePhoneValue = RedisUtil.incr(MOBILE_CONFINE_PREFIX + mobile, 1);
        // ipv6地址的":"会在redis中
        String confineIpKey = ipAddress.replace(":", "_");
        long confineIPValue = RedisUtil.incr(confineIpKey, 1);
        // 一小时5条短信 IP限制 手机号码限制
        if (confinePhoneValue > 5 || confineIPValue > 10) {
            throw new SmsException(1041, "当前操作频繁,请1小时后再试");
        }
        if (confinePhoneValue == 1) {
            RedisUtil.expire(MOBILE_CONFINE_PREFIX + mobile, 3600);
        }
        if (confineIPValue == 1) {
            RedisUtil.expire(confineIpKey, 3600);
        }
    }

    /**
     * 基础发短信方法
     *
     * @param mobile
     * @param templateCode
     * @param code
     * @param codeFormat
     * @return
     * @throws SmsException
     */
    private SendResult sendAliSms(String mobile, String templateCode, String code, String codeFormat) throws SmsException {
        if (acsClient == null) {
            throw new SmsException("短信服务未初始化成功");
        }
        // 组装请求对象
        SendSmsRequest smsRequest = new SendSmsRequest();
        // 使用post提交
        smsRequest.setSysMethod(MethodType.POST);
        // 必填:待发送手机号。支持以逗号分隔的形式进行批量调用，批量上限为1000个手机号码,
        // 批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式；
        // 发送国际/港澳台消息时，接收号码格式为00+国际区号+号码，如“0085200000000”
        smsRequest.setPhoneNumbers(mobile);
        // 必填:短信签名-可在短信控制台中找到
        smsRequest.setSignName(signatrue);
        // 必填:短信模板-可在短信控制台中找到，发送国际/港澳台消息时，请使用国际/港澳台短信模版
        smsRequest.setTemplateCode(templateCode);
        smsRequest.setTemplateParam(codeFormat);
        try {
            SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(smsRequest);
            if ("OK".equals(sendSmsResponse.getCode())) {
                log.info("阿里云短信发送成功[{}: {}]", mobile, templateCode);
                return SendResult.success(code);
            } else {
                log.info("阿里云短信发送失败[{}: {}, code: {}, message: {}]", mobile, templateCode, sendSmsResponse.getCode(), sendSmsResponse.getMessage());
                return SendResult.failed(code, sendSmsResponse.getMessage(), new SmsException("阿里云短信发送失败"));
            }
        } catch (Exception ex) {
            log.error("阿里云短信发生异常", ex);
            return SendResult.failed(code, "阿里云短信发送失败", new SmsException(99, "阿里云短信发送失败"));
        }
    }
}
